#include "../server.h"

#define CODE_MAX_SIZE               8

#define TOKEN_SEQ_SIZE              9
#define TOKEN_RAND_SIZE             6
#define TOKEN_SIZE                  (TOKEN_SEQ_SIZE+TOKEN_RAND_SIZE)
#define TOKEN_ENC_SIZE              (TOKEN_SIZE+1)
#ifdef _MY_DEBUG
static const int MAX_SEQ=100;
static const int MAX_NODE_NUM=10;
#else
static const int MAX_SEQ=999999999;
static const int MAX_NODE_NUM=1000000;
#endif

static OSSL_KEY token_key;

struct _node
{/*{{{*/
    unsigned int seq;
    char code[CODE_MAX_SIZE+1];
    time_t timeout_ts;
    struct rb_link tree;
    struct listp_link list;
};/*}}}*/

static struct
{/*{{{*/
    pthread_mutex_t seq_lock;
    unsigned int seq;

    pthread_mutex_t tree_lock;
    struct rb_tree tree;
    struct listp_link list;
}/*}}}*/
confirm;

int confirm_init()
{/*{{{*/
    char passwd[8+1];
    unsigned int seed=0;
    int res;

    sprintf(passwd, "%03d", random_int(0, 999, &seed));
    sprintf(passwd+3, "%05d", random_int(0, 99999, &seed));
    res=ossl_sym_load_key(OSSL_SYM_AES_128_CBC, OSSL_SYM_PASSWD,
            passwd, &token_key);
    if(res)
    {
        errlog("%s: ossl_sym_load_key fail, res:%d",
                __func__, res);
        return(-1);
    }

    pthread_mutex_init(&confirm.seq_lock, NULL);
    confirm.seq=0;
    pthread_mutex_init(&confirm.tree_lock, NULL);
    rb_create(TREE_KEY_INTEGER, &confirm.tree);
    listp_init(&confirm.list);

    return(0);
}/*}}}*/

extern int get_img_from_pool(char *code, char **datap);

SVC_FUNC(GENERATE)
{/*{{{*/
    char token[TOKEN_SIZE+1];
    char token_enc[TOKEN_ENC_SIZE*2+1];
    int enc_size;
    char code[CODE_MAX_SIZE+1];
    char *data;
    struct _node *node;
    time_t now;
    int res;
    int i;

    pthread_mutex_lock(&confirm.seq_lock);
    ++confirm.seq;
    if(confirm.seq > MAX_SEQ) confirm.seq=0;
    unsigned int seq=confirm.seq;
    pthread_mutex_unlock(&confirm.seq_lock);
    sprintf(token, "%0*d%0*d",
            TOKEN_SEQ_SIZE, seq,
            TOKEN_RAND_SIZE, random_int(0, 999999, NULL));
    res=ossl_sym_encrypt(token_key,
            (unsigned char *)token, TOKEN_SIZE,
            (unsigned char *)token_enc, &enc_size);
    if(res)
    {
        errlog("%s: ossl_sym_encrypt fail, res:%d", __func__, res);
        para_put_item(respond, SOAR_ERR_RESOURCE);
        return(atoi(SOAR_ERR_RESOURCE));
    }
    expand_to_str((unsigned char *)token_enc, enc_size, token_enc);

    // 取验证码及其图片
    ALLOC_DOERR(node, struct _node,
            errlog("%s: alloc node fail, BAD", __func__);
            para_put_item(respond, SOAR_ERR_RESOURCE);
            return(atoi(SOAR_ERR_RESOURCE)));
    res=get_img_from_pool(code, &data);
    if(res)
    {
        errlog("%s: get_img_from_pool fail, res:%d",
                __func__, res);
        free(node);
        para_put_item(respond, SOAR_ERR_RESOURCE);
        return(atoi(SOAR_ERR_RESOURCE));
    }

    // 加入验证缓存
    node->seq=seq;
    strcpy(node->code, code);
    now=curr_timestamp(NULL);
    node->timeout_ts=now+SECOND_OF_HOUR(1);
    listp_init(&node->list);
    tree_setkey_integer(node, seq, tree);

    pthread_mutex_lock(&confirm.tree_lock);     // lock

    res=rb_insert(&confirm.tree, &node->tree);
    if(res)
    {
        if(res == RET_ERR_DUPLICATE)
        {
            errlog("%s: insert into confirm duplicate, impossible",
                    __func__);
        }
        else
        {
            errlog("%s: cannot insert into confirm, res:%d, bad",
                    __func__, res);
        }
    }
    else
    {
        listp_add_tail(&node->list, &confirm.list);
        if(cnfg_info._errlog_.tracking)
            errlog("%s: node(seq:%d code:%s) insert into confirm",
                    __func__, node->seq, node->code);
    }

    // 处理超时
    for(i=0; i<5; ++i)
    {
        struct _node *nodecmp;

        if(listp_is_empty(&confirm.list)) break;
        nodecmp=listp_entry(confirm.list.next, struct _node, list);
        if(nodecmp->timeout_ts > now && confirm.tree.num <= MAX_NODE_NUM)
            break;
        listp_del(&nodecmp->list);
        if(!rb_delete_link(&confirm.tree, &nodecmp->tree))
        {
             errlog("%s: cannot delete from confirm tree, impossible",
                    __func__);
        }
        else
        {
            if(cnfg_info._errlog_.tracking)
                errlog("%s: delete timeout(seq:%d) from confirm tree",
                        __func__, nodecmp->seq);
        }
        free(nodecmp);
    }

    pthread_mutex_unlock(&confirm.tree_lock);   // unlock

    para_put_item(respond, SOAR_ERR_OK);
    para_put_item(respond, token_enc);
    para_put_item(respond, code);
    para_put_item(respond, data);
    free(data);
    return(atoi(SOAR_ERR_OK));
}/*}}}*/

static int _submit(char *request, char *respond, unsigned int update)
{/*{{{*/
    int res;
    char *rtn;

    char para_desc[100];
    PARA para;
    sprintf(para_desc, "token(string:%d) code(string:1-%d)",
            TOKEN_ENC_SIZE*2, CODE_MAX_SIZE); 
    res=para_load(request, para_desc, &para);
    if(res)
    {
        if(res > 0)
            errlog("%s: (%.20s) no.%d para invalid",
                    __func__, request, res);
        else
            errlog("%s: para_load fail, res:%d",
                    __func__, res);
        para_put_item(respond, SOAR_ERR_PARA);
        return(atoi(SOAR_ERR_PARA));
    }
    char *tokenp;
    para_ref_by_name(para, "token", &tokenp);
    char code[CODE_MAX_SIZE+1];
    para_copy_by_name(para, "code", code);
    char token_enc[TOKEN_ENC_SIZE*2+1];
    if(!collapse_from_str(tokenp, TOKEN_ENC_SIZE*2,
                (unsigned char *)token_enc))
    {
        errlog("%s: token(%s) invalid", __func__, tokenp);
        para_free(para);
        para_put_item(respond, SOAR_ERR_PARA);
        return(atoi(SOAR_ERR_PARA));
    }
    para_free(para);

    char token[TOKEN_SIZE+1];
    int token_size;
    res=ossl_sym_decrypt(token_key,
            (unsigned char *)token_enc, TOKEN_ENC_SIZE,
            (unsigned char *)token, &token_size);
    if(res)
    {
        errlog("%s: ossl_sym_decrypt fail, res:%d", __func__, res);
        para_put_item(respond, SOAR_ERR_RESOURCE);
        return(atoi(SOAR_ERR_RESOURCE));
    }
    if(token_size != TOKEN_SIZE)
    {
        errlog("%s: token_size(%d) != TOKEN_SIZE(%d)",
                __func__, token_size, TOKEN_SIZE);
        para_put_item(respond, SOAR_ERR_CONFIRM_SEQ_INVALID);
        return(atoi(SOAR_ERR_CONFIRM_SEQ_INVALID));
    }
    char buf[TOKEN_SEQ_SIZE+1];
    strncpy(buf, token, TOKEN_SEQ_SIZE);
    buf[TOKEN_SEQ_SIZE]=ZERO;
    int seq=atoi(buf);
    if(seq < 0 || seq > MAX_SEQ)
    {
        para_put_item(respond, SOAR_ERR_CONFIRM_SEQ_INVALID);
        return(atoi(SOAR_ERR_CONFIRM_SEQ_INVALID));
    }

    // 寻找seq
    pthread_mutex_lock(&confirm.tree_lock);     // lock

    struct rb_link *tree=rb_find(&confirm.tree, (void *)&seq);
    if(!tree)
    {
        rtn=SOAR_ERR_CONFIRM_SEQ_NOT_FOUND; goto GO_OUT;
    }
    struct _node *node=tree_entry(tree, struct _node, tree);
    if(strcasecmp(node->code, code))
    {
        rtn=SOAR_ERR_CONFIRM_CD_NOT_MATCH; goto GO_OUT;
    }
    if(update)
    {
        listp_del(&node->list);
        if(!rb_delete_link(&confirm.tree, tree))
        {
            errlog("%s: cannot delete from confirm tree, impossible",
                    __func__);
        }
        free(node);
    }
    rtn=SOAR_ERR_OK;
    if(cnfg_info._errlog_.tracking)
        errlog("%s: seq(%d) code(%s) submit succeed",
               __func__, seq, code);

GO_OUT:
    pthread_mutex_unlock(&confirm.tree_lock);   // unlock
    para_put_item(respond, rtn);
    return(atoi(rtn));
}/*}}}*/

SVC_FUNC(SUBMIT)
{/*{{{*/
    return(_submit(request, respond, IS_TRUE));
}/*}}}*/

SVC_FUNC(TRYSUBMIT)
{/*{{{*/
    return(_submit(request, respond, IS_FALSE));
}/*}}}*/
